'''
Created on Oct 3, 2010

@author: morgan
'''
from datetime import datetime, date, tzinfo, timedelta

from __xml__ import createElement
from __string__ import T

from base import KMLObject

__all__ = ['UTCOffset', 'TimePrimitive', 'TimeStamp', 'TimeSpan']

class UTCOffset(tzinfo):
    __ZERO__ = timedelta(0)
    def __init__(self, hours=0, minutes=0):
        self.hours = hours
        self.minutes = minutes
            
    @property
    def hours(self):
        """ Delta in hours from UTC (Zulu time) """
        return self._hours
    
    @hours.setter
    def hours(self, value):
        """ Delta in hours from UTC (Zulu time) """
        if type(value) == int:
            if value in range(-12, 13):
                self._hours = value
            else:
                raise ValueError("hours must be between -12 and 12.")
        else:
            raise TypeError("hours must be an integer")
    
    @property
    def minutes(self):
        """ Delta in minutes from UTC (Zulu time) """
        return self._minutes
    
    @minutes.setter
    def minutes(self, value):
        if type(value) == int:
            if value >= 60:
                self.hours = self.hours + (value / 60)
                self._minutes = value % 60
            elif value <= -60:
                self.hours = self.hours + (value / 60)
                self._minutes = 0 - (abs(value) % 60)
            else:
                self._minutes = value
        else:
            raise TypeError("minutes must be an integer.")
    
    def dst(self, dt):
        return UTCOffset.__ZERO__
    
    def utcoffset(self, dt):
        return self.hours*60 + self.minutes
    
    def __str__(self):
        if self.hours == 0 and self.minutes == 0:
            return "Z"
        else:
            if self.hours == 0:
                if self.minutes < 0:
                    sign = ""
                else:
                    sign = "+"
            elif self.hours < 0:
                sign= ""
            else:
                sign = "+"
            return T("$s$h:$m").substitute(s=sign, h=str(self.hours).zfill(2), 
                                           m=str(self.minutes).zfill(2))


class TimePrimitive(KMLObject):
    def __init__(self, id, *args, **kwds):
        KMLObject.__init__(self, id, *args, **kwds)


class TimeStamp(TimePrimitive):
    """ 
    A few points:
    - This would be a datetime.datetime subclass, except that KML supports dates
      with no day and no month.
    - There is no locale support.  KML represents dates in ISO format, so this
      class supports only ISO format dates.
    - There are some odd years in ancient history in which the leap year rules
      do not apply.  This class doesn't account for them mostly because it 
      probably doesn't need to.  (Show me a use case, and I'll change it.)
    """
    def __init__(self, id="", year=0, month=0, day=0, hour=None, minute=None, 
                 second=None, microsecond=None, tzinfo=None, datetime=None):
        self.__tagName__ = "TimeStamp"
        TimePrimitive.__init__(self, id)
        if year:
            self.year = year
        if month:
            self.month = month
        if day:
            self.day = day
        if hour:
            self.hour = hour
        if minute:
            self.minute = minute
        if second:
            self.second = second
        if tzinfo:
            self.tzinfo = tzinfo
        if datetime:
            self.datetime = datetime
            
    @property
    def year(self):
        return self._year
    
    @year.setter
    def year(self, value):
        if type(value) == int:
            if value > 0:
                self._year = value
            else:
                raise ValueError("year must be positive")
        else:
            raise TypeError("year must be an integer.")
    
    @property
    def month(self):
        return self._month
    
    @month.setter
    def month(self, value):
        if type(value) == int:
            if self.year == 0:
                raise AttributeError("Must specify a year first.")
            if value >= 1 and value <= 12:
                self._month = value
            else:
                raise ValueError(T("Invalid month ($m).").
                                 substitute(m=self.month))
        else:
            raise TypeError("month must be an integer.")
        
    @property
    def day(self):
        if hasattr(self, "_day"):
            return self._day
        else:
            return None
    
    @day.setter
    def day(self, value):
        if type(value) == int:
            if self.month == 0:
                raise AttributeError("Must specify a month first.")
            elif self.month in [1,3,5,7,8,10,12]:
                maxday = 31
            elif self.month == 2:
                if self.year % 4 == 0:
                    maxday = 29
                else:
                    maxday = 28
            else:
                maxday = 30
            if value >= 1 and value <= maxday:
                self._day = value
            else:
                raise ValueError(T("Invalid day for month $m").
                                 substitute(m=self.month))

    @property
    def hour(self):
        if hasattr(self, "_hour"):
            return self._hour
        else:
            return None

    @hour.setter
    def hour(self, value):
        if type(value) == int:
            if value in range(24):
                self._hour = value
            else:
                raise ValueError("Invalid hour specified.")
        else:
            raise TypeError("Hour must be an integer.")
        
    @property
    def minute(self):
        if self.hour is not None:
            if hasattr(self, "_minute"):
                return self._minute
            else:
                return 0
        else:
            return None
    
    @minute.setter
    def minute(self, value):
        if type(value) == int:
            if value in range(60):
                self._minute = value
            else:
                raise ValueError("Invalid minute specified.")
    
    @property
    def second(self):
        if self.hour is not None:
            if hasattr(self, "_second"):
                return self._second
            else:
                return 0
        else:
            return None

    @second.setter
    def second(self, value):
        if type(value) == int:
            if value in range(24):
                self._second = value
            else:
                raise ValueError("hour must be between 0 and 23.")
        else:
            raise TypeError("Hour must be an integer.")
    
    
    @property
    def tzinfo(self):
        if self.hour is not None:
            if hasattr(self, "_tzinfo"):
                return self._tzinfo
            else:
                return UTCOffset(0)
        else:
            return None
    
    @tzinfo.setter
    def tzinfo(self, value):
        if type(value) == UTCOffset:
            self._tzinfo = value
        else:
            raise TypeError("tzinfo must be a kml.UTCOffset object") 
    
    @property
    def datetime(self):
        if not self.month or not self.day or self.hour is not None:
            return None
        else:
            return datetime(self.year, self.month, self.day, self.hour, 
                            self.minute, self.second, 0, self.tzinfo)
            
    @datetime.setter
    def datetime(self, value):
        if type(value) == datetime:
            self.year = value.year
            self.month = value.month
            self.day = value.day
            self.hour = value.hour
            self.minute = value.minute
            self.second = value.second
            tzinfo = UTCOffset(value.tzinfo.utcoffset())
            self.tzinfo = tzinfo
            
    def __str__(self):
        date = ""
        if self.year:
            date = "".join([date, str(self.year).zfill(4)])
            if self.month:
                date = "-".join([date, str(self.month).zfill(2)])
                if self.day:
                    date = "-".join([date, str(self.day).zfill(2)])
                    if self.hour is not None:
                        timeinfo = dict(h=str(self.hour).zfill(2), 
                                        m=str(self.minute).zfill(2), 
                                        s=str(self.second).zfill(2))
                        time = T("$h:$m:$s").substitute(timeinfo)
                        time = "".join([time, str(self.tzinfo)])
                        date = "T".join([date, time])
        return date
    
    def parseDate(self, datetime):
        date = ""
        time = ""
        Z = ""
        msg = T("Invalid timestamp '$s'").substitute(s=datetime)
        if datetime.find("T") >= 0:
            date, time = datetime.split("T")
        else:
            date = datetime
        if date:
            date = date.split("-")
            n = 0
            for x in date:
                date[n] = int(x)
                n += 1
            if len(date) == 3:
                self.year, self.month, self.day = date
            elif len(date) == 2:
                self.year, self.month = date
            elif len(date) == 1:
                self.year = date[0]
            else:
                raise ValueError(msg)
        else:
            raise ValueError(msg)
        if time:
            if time.find("Z") > 0:
                time, Z = time.split("Z")
                if not Z:
                    Z = "Z"
            elif time.find("+") > 0:
                time, Z = time.split("+")
                Z="".join(["+", Z])
            elif time.find("-") > 0:
                time, Z = time.split("-")
                Z="".join(["-", Z])
            time = time.split(":")
            n=0
            for x in time:
                time[n] = int(x)
                n += 1 
            if len(time) == 3:
                self.hour, self.minute, self.second = time
            else:
                raise ValueError(msg)
            tzinfo = UTCOffset()
            if Z == "Z":
                pass
            else:
                Z = Z.split(":")
                n = 0
                for x in Z:
                    Z[n] = int(x)
                    n += 1
                if len(Z) == 2:
                    tzinfo.hours, tzinfo.minutes = Z
                else:
                    raise ValueError(msg)    
                self.tzinfo = tzinfo
                
    value = property(__str__, parseDate)
    
    def toXmlNode(self, kml):
        node = TimePrimitive.toXmlNode(self, kml)
        node.appendChild(createElement(kml._doc, "when", str(self)))
        return node
    
    def parseKMLNode(self, KMLNode):
        TimePrimitive.parseKMLNode(self, KMLNode)
        
        elems = KMLNode.getElementsByTagName("when")
        if elems:
            self.parseDate(elems[0].childNodes[0].data.strip())
        else:
            raise Exception("TimeStamp doesn't specify when!")

    
class TimeSpan(TimePrimitive):
    def __init__(self, id="", begin=None, end=None, *args, **kwds):
        self.__tagName__ = "TimeSpan"
        TimePrimitive.__init__(self, id)
        if begin:
            self.begin = begin
        if end:
            self.end = end
    
    @property
    def begin(self):
        if hasattr(self, "_begin"):
            return self._begin
        else:
            return None
    
    @begin.setter
    def begin(self, value):
        if type(value) == TimeStamp:
            self._begin = value
        elif value is None:
            del self._begin
        else:
            raise TypeError("begin must be a kml.TimeStamp object or None")
    
    @property
    def end(self):
        if hasattr(self, "_end"):
            return self._end
        else:
            return None
    
    @end.setter
    def end(self, value):
        if type(value) == TimeStamp:
            self._end = value
        elif value is None:
            del self._end
        else:
            raise TypeError("begin must be a kml.TimeStamp object or None")
    
    def toXmlNode(self, kml):
        node = TimePrimitive.toXmlNode(self, kml)
        if self.begin:
            node.appendChild(createElement(kml._doc, "begin", str(self.begin)))
        if self.end:
            node.appendChild(createElement(kml._doc, "end", str(self.end)))
        return node
    
    def parseKMLNode(self, KMLNode):
        TimePrimitive.parseKMLNode(self, KMLNode)
        elems = KMLNode.getElementsByTagName("begin")
        if elems:
            begin = TimeStamp()
            begin.parseDate(elems[0].childNodes[0].data.strip())
            self.begin = begin
        elems = KMLNode.getElementsByTagName("end")
        if elems:
            end = TimeStamp()
            end.parseDate(elems[0].childNodes[0].data.strip())
            self.end = end

            
        
        
        
        



        
        